/**
* CPAR TOOLKIT - Compiler and Runtime for CPAR
* Copyright (C) 2012 CPAR TOOLKIT Project Developers
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
#include <stdlib.h>
#include <stdarg.h>
#include "cpar.h"

struct CPARContextStack
{
  void    *data;
  size_t  size;
};

struct CPARContext
{
  CPARContext               *parent;
  CPARContextStack          stack;
  CPARAllocator             *allocator;
  CPARWorkPool              *workPool;
  CPAREvent                 *signal;
  long                      parallels;
  cpar_atomic               pending;
  CPARWorkPoolWorkListener  *workListener;
};

typedef struct CPARContextTaskWorkData
{
  CPARContextFn fn;
  CPARContext   *context;
  long          workers;
  size_t        stackSize;
}CPARContextTaskWorkData;

static void CPARContextTaskWork(void *data);

static void _CPARContextTaskOnBeforeRun(void *data);
static void _CPARContextTaskOnAfterRun(void *data);

static void _CPARContextMicroTaskOnBeforeRun(void *data);
static void _CPARContextMicroTaskOnAfterRun(void *data);

static cpar_status _CPARContextTaskCreate(CPARContext *pParent, CPARContext **pOut, size_t stackSize, long maxWorkers);

cpar_status
  CPARContextCreate( CPARContext *parent, CPARContext **context, size_t stackSize )
{
  cpar_status   s;
  CPARAllocator *pAllocator;
  CPARContext   *pCtx;

  s = CPARAllocatorCreate( &pAllocator );
  if(s)
    return s;

  pCtx = CPARAllocatorAlloc(pAllocator, sizeof(CPARContext));
  if(NULL == pCtx)
  {
    CPARAllocatorDestroy(pAllocator);
    return CPAR_E_NO_MEMORY;
  }

  pCtx->allocator = pAllocator;
  pCtx->parent    = parent;
  pCtx->workPool  = NULL;
  pCtx->parallels = 0;
  pCtx->signal    = NULL;
  pCtx->workListener  = NULL;
  pCtx->pending   = 0;

  pCtx->stack.size = stackSize;
  pCtx->stack.data = CPARAllocatorAlloc(pAllocator,stackSize);
  if(NULL == pCtx->stack.data)
  {
    CPARContextDestroy(pCtx);
    return CPAR_E_NO_MEMORY;
  }

  if(parent)
  {
    pCtx->workPool  = parent->workPool;
    pCtx->parallels = parent->parallels;
  }

  pCtx->workListener = CPARWorkPoolWorkListenerCreate(pAllocator);
  if(NULL == pCtx->workListener)
  {
    CPARContextDestroy(pCtx);
    return CPAR_E_NO_MEMORY;
  }

  pCtx->workListener->onAfterRun = _CPARContextTaskOnAfterRun;
  pCtx->workListener->onBeforeRun = _CPARContextTaskOnBeforeRun;

  s = CPAREventCreate(pAllocator, &(pCtx->signal));
  if(s)
  {
    CPARContextDestroy(pCtx);
    return s;
  }

  *context = pCtx;

  return CPAR_SUCCESS;
}

void 
  CPARContextDestroy( CPARContext *pContext )
{
  if(!pContext->parent && pContext->workPool)
  {
    CPARWorkPoolDestroy(pContext->workPool);
  }

  if(pContext->workListener)
    CPARWorkPoolWorkListenerDestroy(pContext->workListener);

  if(pContext->signal)
    CPAREventDestroy(pContext->signal);

  CPARAllocatorDestroy(pContext->allocator);
}

void  *
  CPARContextStackGet( CPARContext *pContext )
{
  return pContext->stack.data;
}

CPARAllocator *CPARContextAllocatorGet(CPARContext *pContext)
{
  return pContext->allocator;
}

cpar_status 
  CPARContextWorkPoolCreate( CPARContext *ctx, long maxWorkers )
{
  cpar_status s;

  if(ctx->workPool)
    return CPAR_SUCCESS;

  s = CPARWorkPoolCreate(ctx->allocator, &(ctx->workPool), maxWorkers);
  if(s)
    return s;

  ctx->parallels = maxWorkers;

  return s;
}

cpar_status
  CPARContextForAll( CPARContext *ctx, CPARContextFn forAllFn )
{ 
  cpar_status s;
  long        cnt;

  cnt = ctx->parallels;
  cnt--; // CURRENT DOES NOT DISPATCH

  CPAREventReset(ctx->signal);

  CPARAtomicExchange(&(ctx->pending),cnt);

  for(;cnt > 0;cnt--)
  {
    s = CPARWorkPoolDispatch(ctx->workPool, forAllFn, ctx, ctx->workListener);
    if(s)
      break;
  }

  forAllFn(ctx);

  CPARContextTaskWaitAll(ctx);

  return s;
}

cpar_status CPARContextRunParallels(CPARContext *pParent, long cnt, ...)
{
  cpar_status s;
  va_list vl;
  CPARContext *ctx;
  CPARContextFn fn;

  ctx = NULL;

  s = _CPARContextTaskCreate(pParent, &ctx,0,pParent->parallels);
  if(s)
    return s;

  CPAREventReset(ctx->signal);
  CPARAtomicExchange(&(ctx->pending),cnt-1);

  va_start(vl,cnt);
  for(;cnt > 0;cnt--){
    fn = va_arg(vl,CPARContextFn);

    if(cnt == 1){
      fn(ctx);
    }else{
      s = CPARWorkPoolDispatch(ctx->workPool, fn, ctx, ctx->workListener);
      if(s) 
        break;
    }
  }
  va_end(vl);

  CPARContextTaskWaitAll(ctx);

  CPARContextDestroy(ctx);

  return CPAR_SUCCESS;
}

cpar_status
  CPARContextRunBlocks(CPARContext *ctx, long cnt, ...)
{ 
  cpar_status s;
  va_list vl;
  CPARContextFn fn;

  CPAREventReset(ctx->signal);
  CPARAtomicExchange(&(ctx->pending),cnt-1);

  va_start(vl,cnt);
  for(;cnt > 0;cnt--){
    fn = va_arg(vl,CPARContextFn);

    if(cnt == 1){
      fn(ctx);
    }else{
      s = CPARWorkPoolDispatch(ctx->workPool, fn, ctx, ctx->workListener);
      if(s) 
        break;
    }
  }
  va_end(vl);

  CPARContextTaskWaitAll(ctx);

  return CPAR_SUCCESS;
}

static 
  void 
  _CPARContextTaskOnBeforeRun(void *data)
{
  CPARContextTaskWorkData *pData;

  pData = (CPARContextTaskWorkData*)data;

}

static 
  void 
  _CPARContextMicroTaskOnAfterRun(void *data)
{
  CPARContext *ctx;
  ctx = (CPARContext*)data;

  if(CPARAtomicDec(&(ctx->pending)) == 0)
    CPAREventSet(ctx->signal);
}

static 
  void 
  _CPARContextMicroTaskOnBeforeRun(void *data)
{
  CPARContext *ctx;

  ctx = (CPARContext*)data;
}

static 
  void 
  _CPARContextTaskOnAfterRun(void *data)
{
  CPARContextTaskWorkData *pData;
  pData = (CPARContextTaskWorkData*)data;

  if(CPARAtomicDec(&(pData->context->pending)) == 0)
    CPAREventSet(pData->context->signal);
}

cpar_status 
  CPARContextTaskDispatch( CPARContext *ctx, long workers, CPARContextFn taskFn, size_t stackSize )
{
  CPARContextTaskWorkData *data;

  data = (CPARContextTaskWorkData*) CPARAllocatorAlloc(ctx->allocator,sizeof(CPARContextTaskWorkData));
  if(NULL == data)
    return CPAR_E_NO_MEMORY;

  data->context = ctx;
  data->fn      = taskFn;
  data->workers = workers;
  data->stackSize = stackSize;

  CPARAtomicInc(&(ctx->pending));

  return CPARWorkPoolDispatch(ctx->workPool, CPARContextTaskWork, (void*) data, ctx->workListener);
}

static cpar_status _CPARContextTaskCreate(CPARContext *pParent, CPARContext **pOut, size_t stackSize, long maxWorkers){
  cpar_status s;

  s = CPARContextCreate(pParent, pOut, stackSize);
  if(s)
    return s ;

  (*pOut)->parallels = maxWorkers;
  (*pOut)->workListener->onAfterRun = _CPARContextMicroTaskOnAfterRun;
  (*pOut)->workListener->onBeforeRun = _CPARContextMicroTaskOnBeforeRun;

  return CPAR_SUCCESS;
}

static 
  void 
  CPARContextTaskWork( void *data )
{
  cpar_status             s;
  CPARContextTaskWorkData *pWork;
  CPARContext             *pTaskContext;
  CPARContext             *pMyContext;

  pWork = (CPARContextTaskWorkData*)data;
  pMyContext = pWork->context;

  s = _CPARContextTaskCreate(pMyContext,&pTaskContext, pWork->stackSize, pWork->workers);
  if(s)
    return;

  pWork->fn(pTaskContext);

  CPARContextDestroy(pTaskContext);
}

void 
  CPARContextTaskWaitAll( CPARContext *ctx )
{
  CPAREventWait(ctx->signal);
}
